/*
Long Nguyen 
UID: 005-30-140
nguyen.long@utah.edu
Lab 3: 3D Cube
*/

#ifdef __APPLE__
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <GLUT/glut.h>
#elif defined(_WIN32)
#include <windows.h>
#include "glut.h"
//#include "lib/glut.h"
#else
#include <cstdlib>
#include <GL/glut.h>
#endif

#include <cstdio>

using namespace std;

#define XORIG 500
#define YORIG 500
#define ZORIG 0
#define XMAX XORIG + 240
#define YMAX YORIG + 240
#define ZMAX ZORIG + 240
//#define ZEYEMAX  500;
#define YEYEMAX  2000;

int main_window = 1;
float _posX = XORIG;		// initial position of spinwheel's center
float _posY = YORIG;
float _posZ = 0;
int width = 1000;				// window's initial width and height
int height = 1000;
float _size = 240.0;			// width and height of the square
int _scale=1;					// cube's scale factor
float _angleRotation = 0;		// rotation angle
float _angleSmall = 0;
float _speedRotate = 0.4;		// rotation speed
float _speedTrans = 0.5;		// blocks' moving speed

float _side;
float _sideSmall;
float _sideXSmall;

float _eyeX, _eyeY, _eyeZ;
float _yOffset, _zOffset;
float ZEYEMAX = 500;

enum Direction { RIGHT, UP, LEFT, DOWN };
//enum Direction { RIGHT, INTO, LEFT, OUTWARD };
enum Direction _direction = RIGHT;				// moving direction while spinning

//enum Eye { 1, 2, 3, 4 };
int _eyeRotation = 1;

//
// Sets up clear color, shading model, etc...
//
void init(void) {
	
	glClearColor(0,0,0,0);			//Clear background to black 	
	glShadeModel(GL_SMOOTH);		// Use smooth shading.

	//glDepthMask(GL_FALSE);		
	glDisable(GL_LIGHTING);
	glEnable(GL_DEPTH_TEST);		// enable depth buffer
	//glDepthFunc(GL_LEQUAL);	
	//glPolygonMode(GL_FRONT, GL_FILL);		// Draw only front faces and filled.

	_side = _scale*_size;
	_sideSmall = _side/2;
	_sideXSmall = _sideSmall/2;

	_eyeX = 500;
	_eyeY = 1200;
	_eyeZ = 500;

	_yOffset = 0.5;
	_zOffset = 1;
}

void idle(void)
{
	if (glutGetWindow() != main_window)
		glutSetWindow(main_window);

	// just keep redrawing the scene over and over
	glutPostRedisplay();
}

//
// While idle, spins the wheel.
//
void idleRotate(void)
{
	if (glutGetWindow() != main_window)
		glutSetWindow(main_window);

	_angleRotation += _speedRotate;
	_angleSmall = -_angleRotation;
	if (_angleRotation >= 360) {
		_angleRotation -= 360;
		_angleSmall = -_angleRotation;
	}

	// force to redraw
	glutPostRedisplay();
}


void keyboard(unsigned char key, int x, int y)
{
	switch(key) 
	{	
		//// rotate faster		
		//case 'f':
		//case 'F':
		//	_speedRotate += 0.1;
		//	if (_speedRotate > 2) {
		//		_speedRotate = 2;
		//	}
		//	break;
		//
		//// rotate slower
		//case 's':
		//case 'S':
		//	_speedRotate -= 0.1;
		//	if (_speedRotate < 0) {
		//		_speedRotate = 0;
		//	}
		//	break;

		// quit
		case 27:		// esc
		case 'q':
		case 'Q':
			exit(0);
			break;
		

		case 43:		// + (plus), blocks rotate faster
			_speedRotate += 0.1;
			break;

		case 45:		// - (minus), , blocks rotate slower.
			_speedRotate -= 0.1;
			break;
    }

    glutPostRedisplay();
}

// the window has changed shapes, fix ourselves up
void reshape(int x, int y)
{
	width = x;
	height = y;
	printf("%d %d\n", x,y);
	glViewport(0,0,width,height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();		
	gluPerspective(90.0, (float)width/(float)height, 100.0, 5000.0);
	//glOrtho(0,width,0,height,-100,10000);
	glMatrixMode(GL_MODELVIEW);		
    glutPostRedisplay();
}

//
// Translate all blocks along a square path in left, right, up, down directions.
//
void moveBlocks() {
	
	switch (_direction) {
		// is moving RIGHT
		case RIGHT:
			if (_posX + _speedTrans <= XMAX) {
				_posX += _speedTrans;
				glTranslatef(_speedTrans, 0, 0);
			}
			else {
				glTranslatef(XMAX - _posX, 0, 0);				
				_posX = XMAX;
				_direction = UP;
				//_direction = INTO;
			}
			break;

		// spinwheel moves UP
		case UP:
			if (_posY + _speedTrans <= YMAX) {
				_posY += _speedTrans;
				glTranslatef(0, _speedTrans, 0.0);
			}
			else {
				glTranslatef(0, YMAX - _posY, 0.0);
				_posY = YMAX;
				_direction = LEFT;
			}
			break;

		//// is moving INTO
		//case INTO:
		//	// _posZ from 0  to -ZMAX
		//	if (_posZ - _speedTrans >= -ZMAX) {
		//		_posZ -= _speedTrans;
		//		glTranslatef(0, 0, +_speedTrans);
		//		//glTranslatef(0, _speedTrans, 0.0);
		//	}
		//	else {
		//		glTranslatef(0, 0, _posZ + ZMAX );
		//		_posZ = -ZMAX;
		//		_direction = LEFT;
		//	}
		//	break;
		// is moving LEFT
		case LEFT:
			if (_posX - _speedTrans >= XORIG) {
				_posX -= _speedTrans;
				glTranslatef(-_speedTrans, 0, 0);
			}
			else {
				glTranslatef(_posX - XORIG, 0, 0);				
				_posX = XORIG;
				_direction = DOWN;
				//_direction = OUTWARD;
			}
			break;

			// spinwheel moves DOWN
		case DOWN:
			if (_posY - _speedTrans >= YORIG) {
				_posY -= _speedTrans;
				glTranslatef(0, -_speedTrans, 0.0);
			}
			else {
				glTranslatef(_posY - YORIG, 0, 0.0);				
				_posY = YORIG;
				_direction = RIGHT;
			}
			break;

		//// is moving OUT
		//case OUTWARD:
		//	// _posZ from -ZMAX to 0
		//	if (_posZ + _speedTrans <= ZORIG) {
		//		_posZ += _speedTrans;
		//		glTranslatef(0, 0, _speedTrans);
		//	}
		//	else {
		//		glTranslatef(0, 0, ZORIG + _posZ );				
		//		_posZ = ZORIG;
		//		_direction = RIGHT;
		//	}
		//	break;
		default:
			break;
	}
}


void moveEye() {

	//_eyeZ 

	switch (_eyeRotation) {
		case 1:
			_eyeZ -= _zOffset;
			_eyeY += _yOffset;
			if ( _eyeZ < 0 ) {
				//_eyeZ = 0;
				_eyeRotation = 2;
			}
			break;
		case 2:
			_eyeZ -= _zOffset;
			_eyeY -= _yOffset;
			if ( _eyeZ < -ZEYEMAX ) {
				_eyeZ = -ZEYEMAX;
				_eyeRotation = 3;
			}
			break;
		case 3:
			_eyeZ += _zOffset;
			_eyeY -= _yOffset;
			if ( _eyeZ > 0 ) {
				_eyeRotation = 4;
			}
			break;
		case 4:
			_eyeZ += _zOffset;
			_eyeY += _yOffset;
			if ( _eyeZ < -ZEYEMAX ) {
				_eyeZ = -ZEYEMAX;
				_eyeRotation = 1;
			}
			break;
	}
}

//
// Draws a cube.
//
void drawCube(float side) {

	glBegin(GL_QUADS);	
	{							
		// Front square
		glColor3f(0.0,1.0,0.0);						// Green
		glVertex3f( side/2, side/2, side/2 );		// Top Right (Front)
		glColor3f(1.0,0.0,0.0);						// Red
		glVertex3f( -side/2, side/2, side/2 );		// Top Left (Front)
		glColor3f(0.0,0.0,1.0);						// Blue
		glVertex3f( -side/2, -side/2, side/2 );		// Bottom Left (Front)
		glColor3f(1.0,1.0,0.0);						// Yellow
		glVertex3f( side/2, -side/2, side/2 );		// Bottom Right (Front)

		// Back square
		//glColor3f(1.0,0.0,0.0);						// Red
		glColor3f(0.0,1.0,0.0);						// Green
		glVertex3f( side/2, side/2, -side/2 );		// Top Right Of The Quad (Back)
		glColor3f(1.0,0.0,0.0);						// Red
		glVertex3f( -side/2, side/2, -side/2 );		// Top Left Of The Quad (Back)
		glColor3f(0.0,0.0,1.0);						// Blue
		glVertex3f( -side/2, -side/2, -side/2 );	// Bottom Left Of The Quad (Back)
		glColor3f(1.0,1.0,0.0);						// Yellow
		glVertex3f( side/2, -side/2, -side/2 );		// Bottom Right Of The Quad (Back)

		// Top square
		//glColor3f(0.0,0.0,1.0);						// Blue
		glColor3f(0.0,1.0,0.0);						// Green
		glVertex3f( side/2, side/2, -side/2 );		// Top Right (Top)
		glColor3f(1.0,0.0,0.0);						// Red
		glVertex3f( -side/2, side/2, -side/2 );		// Top Left (Top)
		glColor3f(0.0,0.0,1.0);						// Blue
		glVertex3f( -side/2, side/2, side/2 );		// Bottom Left (Top)
		glColor3f(1.0,1.0,0.0);						// Yellow
		glVertex3f( side/2, side/2, side/2 );		// Bottom Right (Top)

		// Bottom square
		//glColor3f(1.0,1.0,0.0);						// Yellow
		glColor3f(0.0,1.0,0.0);						// Green
		glVertex3f( side/2, -side/2, -side/2 );		// Top Right (Bottom)
		glColor3f(1.0,0.0,0.0);						// Red
		glVertex3f( -side/2, -side/2, -side/2 );	// Top Left (Bottom)
		glColor3f(0.0,0.0,1.0);						// Blue
		glVertex3f( -side/2, -side/2, side/2 );		// Bottom Left (Bottom)
		glColor3f(1.0,1.0,0.0);						// Yellow
		glVertex3f( side/2, -side/2, side/2 );		// Bottom Right (Bottom)

		// Left square
		//glColor3f(0.0,1.0,1.0);						// Cyan
		glColor3f(0.0,1.0,0.0);						// Green
		glVertex3f( -side/2, side/2, side/2 );		// Top Right Of The Quad (Left)
		glColor3f(1.0,0.0,0.0);						// Red
		glVertex3f( -side/2, side/2, -side/2 );		// Top Left Of The Quad (Left)
		glColor3f(0.0,0.0,1.0);						// Blue
		glVertex3f( -side/2, -side/2, -side/2 );	// Bottom Left Of The Quad (Left)
		glColor3f(1.0,1.0,0.0);						// Yellow
		glVertex3f( -side/2, -side/2, side/2 );		// Bottom Right Of The Quad (Left)

		// Right square
		//glColor3f(0.5,0.5,0.5);						// Gray
		glColor3f(0.0,1.0,0.0);						// Green
		glVertex3f( side/2, side/2, side/2 );		// Top Right Of The Quad (Right)
		glColor3f(1.0,0.0,0.0);						// Red
		glVertex3f( side/2, side/2, -side/2 );		// Top Left Of The Quad (Right)
		glColor3f(0.0,0.0,1.0);						// Blue
		glVertex3f( side/2, -side/2, -side/2 );		// Bottom Left Of The Quad (Right)
		glColor3f(1.0,1.0,0.0);						// Yellow
		glVertex3f( side/2, -side/2, side/2 );		// Bottom Right Of The Quad (Right)

	}
	glEnd();
}



void display( )
{	
	glMatrixMode(GL_MODELVIEW);	
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);       
	glLoadIdentity();		//	should reset matrix before setting camera
	
	//moveEye();

	// in model space
	gluLookAt(
		//0, 0, 1000.0,			// eye
		//0, 0, 0.0,				// center
		//0.0, 1.0, 0.0				// up		

		_eyeX, _eyeY, _eyeZ,			// eye
		XORIG, YORIG, 0.0,				// center
		0.0, 1.0, 0.0				// up

		//200.0, 300.0, 1000.0,		// eye, used with ortho
		//0.0, 0.0, 0.0,				// center
		//0.0, 1.0, 0.0				// up
		);
	

	// Translate all blocks in left, right, up, down directions.
	moveBlocks();		
	
	// Base cube.
	//
	glTranslatef(_posX, _posY, _posZ);				// Move cubes to center of window.
	//glRotatef(_angleRotation, 0.0, 1.0, 0.0);		// Rotate base about y-axis.
	drawCube(_side);

	//
	// Draws 4 cubes at 4 corners on top of bigger cube
	//

	glPushMatrix();
		// Bottom right cube		
		glTranslatef(_side/2, _side/2 + _sideSmall/2, _side/2);		// Translate to center of this cube
		glRotatef(_angleSmall, 0.0, 1.0, 0.0);						// Rotate in opposite direction with the base cube
		drawCube(_sideSmall);				

		// Smaller cube on top
		glTranslatef(0.0, _sideSmall/2 + _sideXSmall/2 , 0.0);
		glRotatef(2*_angleRotation, 0.0, 1.0, 0.0);
		drawCube(_sideXSmall);

	glPopMatrix();

	glPushMatrix();
		// Bottom left cube
		glTranslatef( -_side/2, _side/2 + _sideSmall/2, _side/2);
		glRotatef(_angleSmall, 0.0, 1.0, 0.0);
		drawCube(_sideSmall);

		// Smaller cube on top
		glTranslatef(0.0, _sideSmall/2 + _sideXSmall/2 , 0.0);
		glRotatef(2*_angleRotation, 0.0, 1.0, 0.0);
		drawCube(_sideXSmall);
	glPopMatrix();

	glPushMatrix();
		// Top right cube
		glTranslatef(_side/2, _side/2 + _sideSmall/2, -_side/2);
		glRotatef(_angleSmall, 0.0, 1.0, 0.0);
		drawCube(_sideSmall);

		// Smaller cube on top
		glTranslatef(0.0, _sideSmall/2 + _sideXSmall/2 , 0.0);
		glRotatef(2*_angleRotation, 0.0, 1.0, 0.0);
		drawCube(_sideXSmall);
	glPopMatrix();

	glPushMatrix();
		// Top left cube
		glTranslatef( -_side/2, _side/2 + _sideSmall/2, -_side/2);
		glRotatef(_angleSmall, 0.0, 1.0, 0.0);
		drawCube(_sideSmall);

		// Smaller cube on top
		glTranslatef(0.0, _sideSmall/2 + _sideXSmall/2 , 0.0);
		glRotatef(2*_angleRotation, 0.0, 1.0, 0.0);
		drawCube(_sideXSmall);
	glPopMatrix();

	glutSwapBuffers();
}

int main(int argc,	char* argv[])
{
	//
	// create the glut window
	//
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE|GLUT_DEPTH);
	glutInitWindowSize(width, height);
	glutInitWindowPosition(100,100);
	main_window = glutCreateWindow("Assignment 2 - Spin wheel");
    glPointSize(1);

	init();

	glutDisplayFunc(display);
    glutKeyboardFunc(keyboard);
    glutReshapeFunc(reshape);
    //glutIdleFunc(idle);

	// make it spin continuously
	glutIdleFunc(idleRotate);

	glMatrixMode(GL_PROJECTION);
    glOrtho(0,width,0,height,-100,10000);
	glMatrixMode(GL_MODELVIEW);
	// give control over to glut
	glutMainLoop();
	
	return 0;
}
